﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;


namespace MultiBoxer
{

    public partial class MainForm : Form
    {
        private static string SEND_COMMAND = "send";
        private static string CONTROL_COMMAND = "multiboxer";
        private static SettingsModel settings;

        private static Dictionary<int, String> buffDict;
        private static Dictionary<int, String> debuffDict;

        #region Imports
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr FindWindowExA(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, IntPtr windowTitle);
        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
        #endregion

        private static DateTime ScanTime = DateTime.Now.AddDays(-1);
        private static TimeSpan TimeSinceScan
        {
            get
            {
                return (DateTime.Now - ScanTime);
            }
        }

        private Dictionary<int, FFXIWindow> instances;

        public MainForm()
        {
            InitializeComponent();
        }

        private Dictionary<int, string> loadDictionary(String path)
        {
            Dictionary<int, string> retDict = new Dictionary<int, string>();

            try
            {
                String[] fileTextLines = System.IO.File.ReadAllLines(path);

                foreach (String line in fileTextLines)
                {
                    try
                    {
                        String[] splitStr = line.Split(' ');
                        retDict.Add(Int16.Parse(splitStr[0]), splitStr[1]);
                    }
                    catch { }
                }

            }
            catch { }

            return retDict;
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            instances = new Dictionary<int, FFXIWindow>();
            settings = new SettingsModel();
            settings.LoadSettings();
            buffDict = loadDictionary("resources\\buffs.txt");
            debuffDict = loadDictionary("resources\\debuffs.txt");
            MainTimer.Start();
        }

        private bool isNewCommand(FFXIWindow instance)
        {
            return WindowerHelper.CCHGetCommandID(instance.windowerHandle) != instance.last_command_id;
        }

        private string ReadArgument(int pid, int index)
        {
            byte[] buf = new byte[2048];
            WindowerHelper.CCHGetArg(pid, (short)index, buf);

            //locate end of argument
            int idx = 0;
            for (idx = 0; idx < buf.Length; idx++)
                if (buf[idx] == 0)
                    break;

            //return shift-jis encoding
            return Encoding.GetEncoding(932).GetString(buf, 0, idx);
        }

        private void ProcessSendCommand(string[] args, int currentPid)
        {
            if (args.Length > 2)
            {
                String sendTo = args[1];

                String command = "";

                for (int idx = 2; idx < args.Length; idx++)
                {
                    if (args[idx - 1].Equals("/ma") ||
                    args[idx - 1].Equals("/magic") ||
                    args[idx - 1].Equals("/item") ||
                    args[idx - 1].Equals("/ja") ||
                    args[idx - 1].Equals("/jobability"))
                    {
                        command += "\"" + args[idx] + "\"";
                    }
                    else
                    {
                        command += args[idx];
                    }
                    if ((idx + 1) != args.Length)
                        command += " ";

                }

                //figure out which char to send the command to
                if (sendTo.StartsWith("@"))
                {
                    if (sendTo.ToLower().Equals("@all"))
                    {
                        List<String> sendList = new List<string>();
                        foreach (FFXIWindow window in instances.Values)
                        {
                            FFACE.PARTYMEMBER pMember = new FFACE.PARTYMEMBER();

                            FFACE.GetPartyMember(window.ffaceHandle, (byte)0, ref pMember);

                            SendCommand(pMember.Name, command);
                        }

                    }
                    else if (sendTo.ToLower().Equals("@others"))
                    {
                        FFACE.PARTYMEMBER thisChar = new FFACE.PARTYMEMBER();

                        FFACE.GetPartyMember(instances[currentPid].ffaceHandle, (byte)0, ref thisChar);

                        List<String> sendList = new List<string>();
                        foreach (FFXIWindow window in instances.Values)
                        {
                            FFACE.PARTYMEMBER pMember = new FFACE.PARTYMEMBER();

                            FFACE.GetPartyMember(window.ffaceHandle, (byte)0, ref pMember);

                            if (!pMember.Name.ToLower().Equals(thisChar.Name.ToLower()))
                            {
                                SendCommand(pMember.Name, command);
                            }
                        }


                    }
                }
                else
                {
                    String[] sends = sendTo.Split('|');

                    foreach (string send in sends)
                    {
                        SendCommand(send, command);
                    }
                }

            }


        }

        private void SendCommand(String character, String command)
        {
            Console.WriteLine("Sending command: " + command + " to " + character);

            //find the character
            foreach (FFXIWindow instance in instances.Values)
            {
                FFACE.PARTYMEMBER thisChar = new FFACE.PARTYMEMBER();

                FFACE.GetPartyMember(instance.ffaceHandle, (byte)0, ref thisChar);

                if (thisChar.Name.ToLower().Equals(character.ToLower()))
                {
                    //match, send the command
                    WindowerHelper.CKHSendString(instance.keyboardHandle, command);
                }
            }

        }

        private void ProcessControlCommand(string[] args)
        {

        }

        private void CheckForNewCommands(FFXIWindow instance)
        {
            if (isNewCommand(instance))
            {
                int argCount = WindowerHelper.CCHGetArgCount(instance.windowerHandle);
                string[] args = new string[argCount];
                for (int i = 0; i < argCount; i++)
                {
                    string arg = ReadArgument(instance.windowerHandle, i);
                    args[i] = arg;

                }

                if (args.Length >= 1 && args[0].ToLower().Equals(SEND_COMMAND))
                {
                    //stuff
                    ProcessSendCommand(args, instance.process.Id);    
                }

                if (args.Length >= 1 && args[0].ToLower().Equals(CONTROL_COMMAND))
                {
                    //stuff
                    ProcessControlCommand(args);
                }

                instance.last_command_id = WindowerHelper.CCHGetCommandID(instance.windowerHandle);
            }
        }

        private void UpdateTextBox(FFXIWindow instance)
        {
            FFACE.PARTYMEMBER thisChar = new FFACE.PARTYMEMBER();
            FFACE.GetPartyMember(instance.ffaceHandle, (byte)0, ref thisChar);

            String boxText = "";

            foreach (FFXIWindow window in instances.Values)
            {
                FFACE.PARTYMEMBER pMember = new FFACE.PARTYMEMBER();
                FFACE.GetPartyMember(window.ffaceHandle, (byte)0, ref pMember);
                FFACE.PLAYERINFO pInfo = new FFACE.PLAYERINFO();
                FFACE.GetPlayerInfo(window.ffaceHandle, ref pInfo);
                if (!pMember.Name.ToLower().Equals(thisChar.Name.ToLower()))
                {
                    boxText += pMember.Name + " - " + pInfo.MainJob.ToString() + "/" + pInfo.SubJob.ToString() +
                        " (" + pMember.CurrentHP + "/" + pInfo.HPMax +
                        ") [" + pMember.CurrentMP + "/" + pInfo.MPMax + "]\n";

                    //get buff list
                    List<String> buffList = new List<string>();
                    List<String> debuffList = new List<string>();
                    FFACE.StatusEffect[] buffs = pInfo.Buffs;
                    foreach (FFACE.StatusEffect buff in buffs)
                    {
                        int buffId = (int)buff;

                        if (buffDict.ContainsKey(buffId))
                        {
                            buffList.Add(buffDict[buffId]);
                        }
                        else if (debuffDict.ContainsKey(buffId))
                        {
                            debuffList.Add(debuffDict[buffId]);
                        }
                    }
                    foreach (String buff in buffList)
                    {
                        boxText += buff + " ";
                    }
                    boxText += "\n";
                    foreach (String debuff in debuffList)
                    {
                        boxText += debuff + " ";
                    }
                    boxText += "\n";

                }
            }

            FFACE.CTHCreateTextObject(instance.ffaceHandle, "MultiBoxer");
            FFACE.CTHSetBGColor(instance.ffaceHandle, "MultiBoxer", (byte)192, (byte)0, (byte)0, (byte)0);
            FFACE.CTHSetBGVisibility(instance.ffaceHandle, "MultiBoxer", true);
            FFACE.CTHSetColor(instance.ffaceHandle, "MultiBoxer", (byte)255, (byte)255, (byte)255, (byte)255);
            FFACE.CTHSetLocation(instance.ffaceHandle, "MultiBoxer", (float)settings.Xpos, (float)settings.Ypos);
            FFACE.CTHSetFont(instance.ffaceHandle, "MultiBoxer", "Arial", 10);
            FFACE.CTHSetText(instance.ffaceHandle, "MultiBoxer", boxText);
            FFACE.CTHFlushCommands(instance.ffaceHandle);
        }
        delegate void SetListCallback(string[] charList);

        void setCharList(string[] charList)
        {
            // avoid accessing the GUI elements from non-foreground threads, by Invoking from the GUI-controlling thread.
            if (this.listBox1.InvokeRequired)
            {
                SetListCallback c = new SetListCallback(setCharList);
                this.Invoke(c, new object[] { charList });
            }
            else
            {
                this.listBox1.BeginUpdate();
                this.listBox1.Items.Clear();
                this.listBox1.Items.AddRange(charList);
                this.listBox1.EndUpdate();
            }
        }

        private void MainTick(object sender, EventArgs e)
        {
            CleanupInstances();

            try
            {
                lock (instances)
                {
                    foreach (FFXIWindow window in instances.Values)
                    {
                        //read and process commands
                        CheckForNewCommands(window);
                        UpdateTextBox(window);
                    }
                }
            }
            catch { }

            if(TimeSinceScan.TotalMinutes>=2)
            {
                FindPOL();
            }

            List<String> charList = new List<string>();

            foreach (FFXIWindow instance in instances.Values)
            {
                FFACE.PARTYMEMBER thisChar = new FFACE.PARTYMEMBER();
                FFACE.GetPartyMember(instance.ffaceHandle, (byte)0, ref thisChar);
                charList.Add(thisChar.Name);
            }

            setCharList(charList.ToArray());

        }

        private void FindPOL()
        {
            CleanupInstances();

            try
            {
                IntPtr hwnd = FindWindowExA(IntPtr.Zero, IntPtr.Zero, "FFXiClass", IntPtr.Zero);

                while (hwnd != IntPtr.Zero)
                {
                    int pid = 0;
                    GetWindowThreadProcessId(hwnd, out pid);

                    if (!instances.ContainsKey(pid))
                    {
                        Console.WriteLine("Found new POL Window: " + pid);
                        lock (instances)
                        {
                            FFXIWindow ffxi = new FFXIWindow();

                            ffxi.ffaceHandle = FFACE.CreateInstance((uint)pid);
                            ffxi.windowerHandle = WindowerHelper.CreateConsoleHelper("WindowerMMFConsoleHandler_" + pid);
                            ffxi.process = System.Diagnostics.Process.GetProcessById(pid);
                            ffxi.keyboardHandle = WindowerHelper.CreateConsoleHelper("WindowerMMFKeyboardHandler_" + pid);
                            ffxi.last_command_id = WindowerHelper.CCHGetCommandID(ffxi.windowerHandle);

                            instances.Add(pid, ffxi);
                        }
                    }

                    hwnd = FindWindowExA(IntPtr.Zero, hwnd, "FFXiClass", IntPtr.Zero);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            ScanTime = DateTime.Now;

        }

        private void CleanupInstances()
        {
            lock (instances)
            {
                List<int> instanceDeleteList = new List<int>();
                foreach (FFXIWindow window in instances.Values.Where(wnd => wnd.process.HasExited))
                {
                    Console.WriteLine("Removing " + window.process.Id);
                    instanceDeleteList.Add(window.process.Id);
                }

                foreach (int i in instanceDeleteList)
                {
                    instances.Remove(i);
                }
            }
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            lock (instances)
            {
                foreach (FFXIWindow window in instances.Values)
                {
                    FFACE.CTHDeleteTextObject(window.ffaceHandle, "MultiBoxer");
                    FFACE.CTHFlushCommands(window.ffaceHandle);
                }
            }
        }

    }
}
